Основная задача - построить сервис, который может прогнозировать ЗП, основываясь на данных по профессии, региону размещения, графику работы и другим дотсупным данным.
Требования к модели: модель должна быть интерпетируемой, т.е. мы дожны уметь показать Пользователю основные факторы и их вес в формировании зарплаты. Второе требование - метрика mape <= 0.2.
Основные этапы работы:
EDA. Нужно понять с какими данными работаем, какие из фичей влияют на зарплату. На этом этапе важно понять, правильно ли мы собрали данные? Не ли перекосов в распределениях по локациям? Например у нас 15к вакансий из Мск, 5к вакансий из Питера, а остальные - все остальные регионы РФ. Или у нас в выборке большой перекос в сторону популярыных профессий: кассиров и водителей, а инженеров очень мало.
Обработка данных. Нужно избавиться от выбросов, написать пайплайны для процессинга данных. Сформировать трейн и тест выборки.
Моделирование. Эксперементируем с моделями, для того чтобы выбить на тесте mape <= 0.2, не забывая что модель должна быть интерпретируемой.
Валидация. Проверяем адекватность работы модели.
from google.colab import drive
drive.mount('/content/drive')
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).
%%capture
!pip install https://github.com/pandas-profiling/pandas-profiling/archive/master.zip
#重新启动内核
import os
os._exit(00)
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
#from pandas_profiling import ProfileReport
from ydata_profiling import ProfileReport
df = pd.read_csv('/content/drive/MyDrive/研一_项目研讨_预测工资/data_vacancies.csv')
df.head()
| id | custom_position | schedule | salary_from | salary_to | salary_pay_type | offer_education_id | education_name | education_is_base | education_order_num | city_id | list_regions | work_skills | tags_id | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 48202096 | Сварщик-сборщик | полный рабочий день | 60000 | 120000 | NaN | 0 | любое | True | 0 | 2 | [4] | ['сварочные работы', 'сборка изделий по чертеж... | NaN |
| 1 | 48202097 | Сварщик-монтажник | полный рабочий день | 60000 | 120000 | NaN | 0 | любое | True | 0 | 2 | [4] | ['монтажные работы', 'строительные работы', 'э... | NaN |
| 2 | 48202098 | Слесарь-сборщик | полный рабочий день | 60000 | 80000 | NaN | 0 | любое | True | 0 | 2 | [4] | ['работа на фрезерных станках', 'слесарный рем... | NaN |
| 3 | 48202356 | Грузчик-упаковщик | частичная занятость | 30000 | 35000 | NaN | 0 | любое | True | 0 | 1 | [3] | ['комплектация товара', 'маркировка', 'стрессо... | [6, 9] |
| 4 | 48202357 | Грузчик-упаковщик | частичная занятость | 30000 | 35000 | NaN | 0 | любое | True | 0 | 57 | [181, 182, 183, 185, 186, 187, 188, 189, 190, ... | ['маркировка', 'стрессоустойчивость', 'погрузо... | [6, 9] |
df.shape
(19489, 14)
df.isnull().sum()
id 0 custom_position 0 schedule 0 salary_from 0 salary_to 0 salary_pay_type 19383 offer_education_id 0 education_name 0 education_is_base 0 education_order_num 0 city_id 0 list_regions 0 work_skills 0 tags_id 5999 dtype: int64
df['salary_to'].describe()
count 19489.000 mean 88490.884 std 55438.161 min 21000.000 25% 51000.000 50% 73000.000 75% 107000.000 max 1200000.000 Name: salary_to, dtype: float64
df['education_name'].unique()
array(['любое', 'среднее', 'высшее', 'среднее профессиональное',
'неполное высшее'], dtype=object)
df['schedule'].unique()
array(['полный рабочий день', 'частичная занятость', 'удаленная работа',
'сменный график', 'свободный график', 'вахта'], dtype=object)
print(f"Всего вакансий в этих данных: {len(df['custom_position']):,}")
Всего вакансий в этих данных: 19,489
pd.set_option('float_format', lambda x: '{:.3f}'.format(x))
df['salary_from'].describe()
count 19489.000 mean 58869.139 std 30248.195 min 20500.000 25% 40000.000 50% 50000.000 75% 70000.000 max 750000.000 Name: salary_from, dtype: float64
eda_data.columns
Index(['id', 'custom_position', 'schedule', 'salary_from', 'salary_to',
'salary_pay_type', 'offer_education_id', 'education_name',
'education_is_base', 'education_order_num', 'city_id', 'list_regions',
'work_skills', 'tags_id', 'graphic'],
dtype='object')
*Что предположим, если тип зарплаты (salary_pay_type) не указан? Поставить "gros"? Зарплату за тип: "gros", "net".*
pd.set_option('display.float_format', lambda x:'%.6f'%x)
df.describe()
| id | salary_from | salary_to | offer_education_id | education_order_num | city_id | |
|---|---|---|---|---|---|---|
| count | 19489.000000 | 19489.000000 | 19489.000000 | 19489.000000 | 19489.000000 | 19489.000000 |
| mean | 48505170.910411 | 58869.138848 | 88490.883935 | 0.351429 | 2.474473 | 22.559495 |
| std | 164298.572357 | 30248.195246 | 55438.161312 | 0.970259 | 6.482314 | 38.693556 |
| min | 48202096.000000 | 20500.000000 | 21000.000000 | 0.000000 | 0.000000 | 1.000000 |
| 25% | 48353880.000000 | 40000.000000 | 51000.000000 | 0.000000 | 0.000000 | 1.000000 |
| 50% | 48513907.000000 | 50000.000000 | 73000.000000 | 0.000000 | 0.000000 | 2.000000 |
| 75% | 48668409.000000 | 70000.000000 | 107000.000000 | 0.000000 | 0.000000 | 57.000000 |
| max | 48737872.000000 | 750000.000000 | 1200000.000000 | 4.000000 | 25.000000 | 272.000000 |
education_map = {
'любое': 1,
'среднее': 2,
'высшее': 3,
'среднее профессиональное': 4,
'неполное высшее': 5
}
eda_data = df
eda_data['education_name'] = eda_data['education_name'].replace(education_map)
eda_data['graphic'] = eda_data['schedule'].map(lambda x: {
'полный рабочий день': 1,
'частичная занятость': 2,
'удаленная работа': 3,
'сменный график': 4,
'свободный график': 5,
'вахта': 6
}[x])
eda_data.head()
| id | custom_position | schedule | salary_from | salary_to | salary_pay_type | offer_education_id | education_name | education_is_base | education_order_num | city_id | list_regions | work_skills | tags_id | graphic | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 48202096 | Сварщик-сборщик | полный рабочий день | 60000 | 120000 | NaN | 0 | 1 | True | 0 | 2 | [4] | ['сварочные работы', 'сборка изделий по чертеж... | NaN | 1 |
| 1 | 48202097 | Сварщик-монтажник | полный рабочий день | 60000 | 120000 | NaN | 0 | 1 | True | 0 | 2 | [4] | ['монтажные работы', 'строительные работы', 'э... | NaN | 1 |
| 2 | 48202098 | Слесарь-сборщик | полный рабочий день | 60000 | 80000 | NaN | 0 | 1 | True | 0 | 2 | [4] | ['работа на фрезерных станках', 'слесарный рем... | NaN | 1 |
| 3 | 48202356 | Грузчик-упаковщик | частичная занятость | 30000 | 35000 | NaN | 0 | 1 | True | 0 | 1 | [3] | ['комплектация товара', 'маркировка', 'стрессо... | [6, 9] | 2 |
| 4 | 48202357 | Грузчик-упаковщик | частичная занятость | 30000 | 35000 | NaN | 0 | 1 | True | 0 | 57 | [181, 182, 183, 185, 186, 187, 188, 189, 190, ... | ['маркировка', 'стрессоустойчивость', 'погрузо... | [6, 9] | 2 |
numeric_df = eda_data.select_dtypes(include=['int', 'float'])
correlation_matrix = numeric_df.corr()
plt.figure(figsize=(18, 16))
sns.heatmap(
correlation_matrix,
annot=True,
cmap='coolwarm',
fmt=".2f",
vmin=-1,
vmax=1,
center=0,
linewidths=0.5)
plt.title('Correlation Matrix of Numeric Columns')
plt.xlabel('X')
plt.ylabel('Y')
plt.xticks(rotation=45, ha='right')
plt.show()
df.isnull().sum()
id 0 custom_position 0 schedule 0 salary_from 0 salary_to 0 salary_pay_type 19383 offer_education_id 0 education_name 0 education_is_base 0 education_order_num 0 city_id 0 list_regions 0 work_skills 0 tags_id 5999 dtype: int64
test = df[df['salary_pay_type'].notna()]
test.head()
| id | custom_position | schedule | salary_from | salary_to | salary_pay_type | offer_education_id | education_name | education_is_base | education_order_num | city_id | list_regions | work_skills | tags_id | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 531 | 48223566 | Менеджер по продажам окон и жалюзи | полный рабочий день | 60000 | 100000 | net | 4 | высшее | True | 10 | 57 | [1269] | ['оформление заказов'] | NaN |
| 704 | 48230735 | Менеджер по продажам b2b | полный рабочий день | 60000 | 120000 | net | 3 | неполное высшее | True | 15 | 2 | [4] | ['входящие звонки'] | [9] |
| 817 | 48232436 | Дизайнер кухонь | свободный график | 100000 | 200000 | net | 4 | высшее | True | 10 | 1 | [3] | ['1'] | [9] |
| 954 | 48237920 | Инженер по ремонту МФУ, плотеров и принтеров | полный рабочий день | 55000 | 85000 | net | 0 | любое | True | 0 | 1 | [3] | ['Опыт ремонта оргтехники'] | NaN |
| 1504 | 48260331 | Менеджер записи на пробный урок в международну... | полный рабочий день | 30000 | 50000 | net | 0 | любое | True | 0 | 57 | [204] | ['входящие звонки', 'исходящие звонки'] | NaN |
corr_matrix=df.corr()
plt.subplots(figsize=(15,8))
fig=sns.heatmap(corr_matrix, annot=True, square=True, cmap='RdBu', fmt='.4g') #annot为热力图上显示数据, fmt='.2g'为数据保留两位有效数字, square呈现正方形, vmax最大值为1
fig;
<ipython-input-46-7a11d84963f5>:1: FutureWarning: The default value of numeric_only in DataFrame.corr is deprecated. In a future version, it will default to False. Select only valid columns or specify the value of numeric_only to silence this warning. corr_matrix=df.corr()
report=ProfileReport(df)
report
Summarize dataset: 0%| | 0/5 [00:00<?, ?it/s]
Generate report structure: 0%| | 0/1 [00:00<?, ?it/s]
Render HTML: 0%| | 0/1 [00:00<?, ?it/s]
sample_df = df.sample(n=100)
| id | custom_position | schedule | salary_from | salary_to | salary_pay_type | offer_education_id | education_name | education_is_base | education_order_num | city_id | list_regions | work_skills | tags_id | graphic | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 13441 | 48634020 | Сборщик кухонной мебели | свободный график | 170000 | 200000 | NaN | 0 | 1 | 1 | 0 | 1 | [3] | ['сборка мебели', 'пунктуальность', 'стрессоус... | NaN | 5 |
| 16886 | 48704160 | Рубщик мяса (г. Сосновый Бор) | сменный график | 44250 | 93000 | NaN | 0 | 1 | 1 | 0 | 102 | [174] | ['Разруб мяса'] | NaN | 4 |
| 13552 | 48638065 | Фотограф | полный рабочий день | 35000 | 80000 | NaN | 0 | 1 | 1 | 0 | 2 | [4] | ['съемка детей', 'портретная съемка', 'Adobe P... | NaN | 1 |
| 8731 | 48481735 | Продавец-кассир (ул. Советская дом 47) | сменный график | 44206 | 48627 | NaN | 0 | 1 | 1 | 0 | 57 | [1220] | ['без опыта', 'доброжелательность'] | [9] | 4 |
| 8796 | 48485078 | Курьер | частичная занятость | 25000 | 100000 | NaN | 0 | 1 | 1 | 0 | 1 | [3] | ['трудолюбие', 'вежливость', 'коммуникабельнос... | [6, 9] | 2 |
import matplotlib.pyplot as plt
import seaborn as sns
def create_boxplot(data, x_column, y_column, title, figsize=(18, 8)):
"""Creates a boxplot using Seaborn.
Args:
data: The data to use to create the boxplot.
x_column: The name of the x-axis column.
y_column: The name of the y-axis column.
title: The title of the boxplot.
figsize: The figure size of the plot.
"""
plt.figure(figsize=figsize)
sns.boxplot(
x=x_column,
y=y_column,
showmeans=True,
notch=True,
whis=1.5,
data=data,
)
plt.xticks(rotation=-5)
plt.title(title)
plt.show()
# Create the boxplots.
create_boxplot(sample_df, "schedule", "salary_to", "Salary Distribution by Schedule Level")
create_boxplot(sample_df, "schedule", "salary_from", "Salary Distribution by Schedule Level")
create_boxplot(sample_df, "education_name", "salary_to", "Salary Distribution by Education Level")
create_boxplot(sample_df, "education_name", "salary_from", "Salary Distribution by Education Level")
plt.figure(figsize=(18, 18))
sns.boxplot(
x='schedule',
y='salary_from',
showmeans=True,
notch=True,
whis=1.5,
data=sample_df)
plt.xticks(rotation=-25)
plt.title('Salary Distribution by Schedule Level')
plt.show()
def create_scatter_plot(data, x_column, y_column, title, palette):
"""Creates a scatter plot using Seaborn.
Args:
data: The data to use to create the scatter plot.
x_column: The name of the x-axis column.
y_column: The name of the y-axis column.
title: The title of the scatter plot.
palette: The color palette to use for the scatter plot.
"""
sns.scatterplot(
x=x_column,
y=y_column,
alpha=0.5,
palette=palette,
data=data,
)
plt.xlabel(x_column)
plt.ylabel(y_column)
plt.title(title)
plt.show()
# Create the scatter plots.
create_scatter_plot(eda_data, "education_name", "salary_to", "Scatter Plot: Salary vs. Education Level (Salary To)", "viridis")
create_scatter_plot(eda_data, "education_name", "salary_from", "Scatter Plot: Salary vs. Education Level (Salary From)", "inferno")
<ipython-input-65-0a3e98970c34>:12: UserWarning: Ignoring `palette` because no `hue` variable has been assigned. sns.scatterplot(
<ipython-input-65-0a3e98970c34>:12: UserWarning: Ignoring `palette` because no `hue` variable has been assigned. sns.scatterplot(
График показывает, что существует положительная корреляция между уровнем образования и заработной платой. Это означает, что люди с более высоким уровнем образования, скорее всего, будут зарабатывать больше. Однако корреляция не идеальна.
Некоторые люди с более низким уровнем образования зарабатывают больше, чем люди с более высоким уровнем образования, но в целом люди с более высоким уровнем образования зарабатывают больше.
Также стоит отметить, что распределение заработной платы для каждого уровня образования асимметрично вправо, что означает, что больше людей зарабатывают более низкую заработную плату, чем людей, зарабатывающих более высокую заработную плату.
report.to_widgets()
/usr/local/lib/python3.10/dist-packages/ydata_profiling/profile_report.py:507: UserWarning: Ipywidgets is not yet fully supported on Google Colab (https://github.com/googlecolab/colabtools/issues/60).As an alternative, you can use the HTML report. See the documentation for more information. warnings.warn(
VBox(children=(Tab(children=(Tab(children=(GridBox(children=(VBox(children=(GridspecLayout(children=(HTML(valu…
report.to_notebook_iframe()
eda_data['education_is_base'] = eda_data['education_is_base'].astype(int)
eda_data.columns
Index(['id', 'custom_position', 'schedule', 'salary_from', 'salary_to',
'salary_pay_type', 'offer_education_id', 'education_name',
'education_is_base', 'education_order_num', 'city_id', 'list_regions',
'work_skills', 'tags_id', 'graphic'],
dtype='object')
columns_to_pick = ['id', 'custom_position', 'salary_from', 'salary_to', 'offer_education_id', 'education_name',
'education_is_base', 'education_order_num', 'city_id', 'education_level', 'schedule']
eda_data = eda_data.filter(columns_to_pick)
eda_data
| id | custom_position | salary_from | salary_to | offer_education_id | education_name | education_is_base | education_order_num | city_id | schedule | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 48202096 | Сварщик-сборщик | 60000 | 120000 | 0 | 1 | 1 | 0 | 2 | полный рабочий день |
| 1 | 48202097 | Сварщик-монтажник | 60000 | 120000 | 0 | 1 | 1 | 0 | 2 | полный рабочий день |
| 2 | 48202098 | Слесарь-сборщик | 60000 | 80000 | 0 | 1 | 1 | 0 | 2 | полный рабочий день |
| 3 | 48202356 | Грузчик-упаковщик | 30000 | 35000 | 0 | 1 | 1 | 0 | 1 | частичная занятость |
| 4 | 48202357 | Грузчик-упаковщик | 30000 | 35000 | 0 | 1 | 1 | 0 | 57 | частичная занятость |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 19484 | 48737855 | Кладовщик | 45000 | 70000 | 2 | 4 | 1 | 20 | 1 | полный рабочий день |
| 19485 | 48737859 | Кассир | 35000 | 58000 | 0 | 1 | 1 | 0 | 1 | сменный график |
| 19486 | 48737860 | Инженер по медицинской технике | 77000 | 77000 | 4 | 3 | 1 | 10 | 1 | полный рабочий день |
| 19487 | 48737871 | Автомеханик-автослесарь | 80000 | 120000 | 0 | 1 | 1 | 0 | 2 | полный рабочий день |
| 19488 | 48737872 | Автомеханик-автослесарь | 80000 | 120000 | 0 | 1 | 1 | 0 | 102 | полный рабочий день |
19489 rows × 10 columns
В качестве y будем использовать поле salary_from
Спикок кодировок регионов и тэгов - в отдельном справочнике.
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(style="whitegrid")
plt.style.use('ggplot')
# DATA_FOLDER = os.path.join('data') # путь к директории с данными
from google.colab import files
uploaded = files.upload()
data = pd.read_csv(os.path.join(DATA_FOLDER, 'data_vacancies.csv'))
data.head(3)
| id | custom_position | schedule | salary_from | salary_to | salary_pay_type | offer_education_id | education_name | education_is_base | education_order_num | city_id | list_regions | work_skills | tags_id | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 48202096 | Сварщик-сборщик | полный рабочий день | 60000 | 120000 | NaN | 0 | любое | True | 0 | 2 | [4] | ['сварочные работы', 'сборка изделий по чертеж... | NaN |
| 1 | 48202097 | Сварщик-монтажник | полный рабочий день | 60000 | 120000 | NaN | 0 | любое | True | 0 | 2 | [4] | ['монтажные работы', 'строительные работы', 'э... | NaN |
| 2 | 48202098 | Слесарь-сборщик | полный рабочий день | 60000 | 80000 | NaN | 0 | любое | True | 0 | 2 | [4] | ['работа на фрезерных станках', 'слесарный рем... | NaN |
data.describe(include='all').T['count']
id 19489.0 custom_position 19489 schedule 19489 salary_from 19489.0 salary_to 19489.0 salary_pay_type 106 offer_education_id 19489.0 education_name 19489 education_is_base 19489 education_order_num 19489.0 city_id 19489.0 list_regions 19489 work_skills 19489 tags_id 13490 Name: count, dtype: object
print(f"Общее кол-во вакансий - {len(data):,}.")
Общее кол-во вакансий - 19,489.
data['salary_from'].describe()
count 19489.000000 mean 58869.138848 std 30248.195246 min 20500.000000 25% 40000.000000 50% 50000.000000 75% 70000.000000 max 750000.000000 Name: salary_from, dtype: float64
top20_profs = data['custom_position'].value_counts(dropna=False).nlargest(20)
top20_profs
Продавец-кассир 409 Менеджер по продажам 290 Продавец-консультант 238 Курьер 193 Охранник 134 Повар 130 Разнорабочий 127 Водитель по доставке документов 118 Грузчик 118 Комплектовщик 112 Работник торгового зала 105 Продавец 96 Кладовщик 96 Менеджер по работе с клиентами 95 Оператор call-центра / Менеджер по работе с клиентами (удаленно) 95 Оператор call-центра 94 Мерчандайзер-грузчик 94 Оператор входящих звонков 87 Водитель-экспедитор 87 Оператор call-центра (удаленно) 78 Name: custom_position, dtype: int64
data[data['city_id'] == 1]['custom_position'].value_counts(dropna=False).nlargest(20)
Менеджер по продажам 179 Продавец-кассир 166 Курьер 123 Продавец-консультант 115 Повар 86 Оператор входящих звонков 80 Копирайтер 72 Контент-менеджер 65 Менеджер социальных сетей 58 Помощник копирайтера 55 Работник торгового зала 55 Мерчандайзер-грузчик 54 Оператор call-центра 52 Грузчик 52 Охранник 50 Уборщик/ца 49 Помощник разработчика сайтов 49 Кладовщик 48 Помощник контент-менеджера 47 Менеджер по работе с клиентами магазина бытовой техники и электроники (Входящие звонки) 47 Name: custom_position, dtype: int64
data[data['city_id'] == 2]['custom_position'].value_counts(dropna=False).nlargest(20)
Продавец-кассир 67 Менеджер по продажам 50 Охранник 44 Продавец-консультант 44 Курьер 42 Домработница приходящая 35 Уборщик, Уборщица 28 Разнорабочий 26 Кладовщик 26 Продавец 21 Уборщик/Уборщица 21 Работник торгового зала 19 Грузчик 18 Менеджер по работе с клиентами 18 Бариста 17 Сборщик интернет-заказов 17 Администратор 15 Электромонтажник 15 Заместитель директора магазина 15 Автомеханик-автослесарь 15 Name: custom_position, dtype: int64
_df = data[data['custom_position'].isin(top20_profs.index)]
profession_ranking = list(top20_profs.index)
f, ax = plt.subplots(figsize=(12,5))
ax = sns.boxenplot(x="custom_position", y="salary_from",
color="gray", palette="Set3", order=profession_ranking,
scale="linear", data=_df, linewidth=0.5)
ax.tick_params(axis='x', rotation=90)
ax.set_title("Зарплата по ТОП 20 професиям", fontsize=14)
means = _df.groupby("custom_position")["salary_from"].mean().loc[profession_ranking]
_ = plt.plot(range(len(profession_ranking)), means, marker="o", color="green", markersize=6, linestyle="--")
from sklearn.model_selection import train_test_split
data_train, data_test, y_train, y_test = train_test_split(data[['custom_position']],
data[['salary_from']],
test_size=0.2,
random_state=42)
data_train.shape, data_test.shape
((15591, 1), (3898, 1))
import fasttext
import fasttext.util
import numpy as np
from scipy import spatial
ft = fasttext.load_model('../cc.ru.300.bin')
Warning : `load_model` does not return WordVectorModel or SupervisedModel any more, but a `FastText` object which is very similar.
fasttext.util.reduce_model(ft, 100) # уменьшаем размерность вектора
<fasttext.FastText._FastText at 0x7fa00ae37850>
ft.get_dimension()
100
ft.get_word_vector('водитель')[:10]
array([-0.02232989, -0.02558348, 0.0685254 , 0.04912743, 0.01441588,
0.01026478, -0.10471214, 0.03264587, 0.05108723, 0.11439214],
dtype=float32)
def get_cosine_similarity(a: list, b: list):
return 1 - spatial.distance.cosine(a, b)
def get_vector(s, model=ft):
s = s.lower().strip()
return model.get_word_vector(s)
get_vector('Водитель ')[:10]
array([-0.02232989, -0.02558348, 0.0685254 , 0.04912743, 0.01441588,
0.01026478, -0.10471214, 0.03264587, 0.05108723, 0.11439214],
dtype=float32)
get_cosine_similarity(get_vector('водитель'), get_vector('таксист'))
0.8104820251464844
get_cosine_similarity(get_vector('тракторист'), get_vector('фотомодель'))
0.3793732523918152
from sklearn.preprocessing import FunctionTransformer
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.pipeline import Pipeline
from sklearn.neighbors import KNeighborsRegressor
from sklearn.model_selection import KFold, GridSearchCV
from sklearn.metrics import r2_score, mean_absolute_percentage_error
class DataFrameSelector(BaseEstimator, TransformerMixin):
def __init__(self, attribute_names):
self.attribute_names = attribute_names
def fit(self, X, y=None):
return self
def transform(self, X):
return X[self.attribute_names]
class ProcessingTextFeatures(BaseEstimator, TransformerMixin):
def __init__(self):
pass
def fit(self, X, y=None):
return self
@staticmethod
def text_processing(s):
if s:
return s.lower().strip()
else:
return None
def transform(self, X):
for a in X.columns:
X[a] = X[a].apply(lambda x: self.text_processing(x))
return X
class TextEmbeddings(BaseEstimator, TransformerMixin):
def __init__(self, lang_model):
self.lang_model = lang_model
def fit(self, X, y=None):
return self
@staticmethod
def get_features_from_vector(vectors):
df = pd.DataFrame()
for v in vectors:
df = pd.concat([df, pd.DataFrame(v).T])
_cols = [f"feature_{c}" for c in df.columns]
df.columns = _cols
return df
def transform(self, X):
X['vector_professions'] = X.iloc[:,0].apply(
lambda words: np.mean([get_vector(word) for word in words.split(" ")], axis=0))
return self.get_features_from_vector(X['vector_professions'])
profession_embedding_pipeline = Pipeline([
("select_features", DataFrameSelector(attribute_names=["custom_position"])),
("processing_text_features", ProcessingTextFeatures()),
("get_profession_embeddings", TextEmbeddings(lang_model=ft)),
])
X_train = profession_embedding_pipeline.fit_transform(data_train)
X_train.head(3)
| feature_0 | feature_1 | feature_2 | feature_3 | feature_4 | feature_5 | feature_6 | feature_7 | feature_8 | feature_9 | ... | feature_90 | feature_91 | feature_92 | feature_93 | feature_94 | feature_95 | feature_96 | feature_97 | feature_98 | feature_99 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.015912 | 0.001667 | 0.024425 | -0.033466 | 0.028346 | 0.002949 | -0.009210 | 0.010261 | 0.012796 | 0.009221 | ... | 0.002755 | -0.005422 | 0.022568 | -0.017908 | 0.009551 | 0.033006 | -0.006285 | 0.021489 | -0.007438 | -0.028621 |
| 0 | 0.012033 | 0.064572 | 0.030388 | -0.082866 | -0.061539 | 0.052734 | -0.057564 | 0.055990 | -0.013068 | -0.059835 | ... | -0.006975 | 0.030193 | -0.012232 | -0.028487 | -0.031524 | -0.013782 | 0.049508 | 0.043902 | 0.024200 | 0.045752 |
| 0 | -0.002965 | 0.054473 | 0.010346 | -0.056326 | -0.138993 | 0.012678 | 0.025266 | 0.074584 | 0.061354 | -0.024473 | ... | 0.015798 | 0.037486 | 0.035533 | -0.017324 | -0.044231 | -0.040195 | 0.052769 | 0.008908 | 0.012552 | 0.083026 |
3 rows × 100 columns
num_folds = 5
seed = 7
scoring = 'neg_mean_absolute_error'
neighbors = [1, 3, 5, 7, 9, 15]
param_grid = dict(n_neighbors=neighbors)
model = KNeighborsRegressor()
kfold = KFold(n_splits=num_folds)
grid = GridSearchCV(estimator=model, param_grid=param_grid, scoring=scoring, cv=kfold)
grid_result = grid.fit(X_train, y_train)
print("Best: %f using %s" % (-1 * grid_result.best_score_, grid_result.best_params_))
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
print("MAE: %f (%f) with: %r" % (-1 * mean, stdev, param))
Best: 13900.328053 using {'n_neighbors': 5}
MAE: 15016.689200 (399.050735) with: {'n_neighbors': 1}
MAE: 14012.989705 (346.416102) with: {'n_neighbors': 3}
MAE: 13900.328053 (361.543882) with: {'n_neighbors': 5}
MAE: 14064.296092 (295.366199) with: {'n_neighbors': 7}
MAE: 14186.445977 (260.040995) with: {'n_neighbors': 9}
MAE: 14561.963819 (224.822423) with: {'n_neighbors': 15}
#evaluation - baselines
num_folds = 5
seed = 7
scoring = 'neg_mean_absolute_error'
models = []
models.append(('LR', LinearRegression()))
models.append(('RidgeRegression', Ridge()))
models.append(('LassoRegression', Lasso()))
models.append(('KNNRegression', KNeighborsRegressor()))
models.append(('DecisionTreeRegressor', DecisionTreeRegressor()))
results = []
names = []
for name, model in models:
kfold = KFold(n_splits=num_folds, random_state=seed, shuffle=True)
# умножаем на (-1) из-за особеностей работы cross_val_score
cv_results = -1 * cross_val_score(model, X_train, y_train, cv=kfold, scoring=scoring)
results.append(cv_results)
names.append(name)
msg = "%s %f %f " % (name, cv_results.mean(), cv_results.std())
print(msg)
best_knn = grid.best_estimator_
KNeighborsRegressor()
preds = best_knn.predict(profession_embedding_pipeline.fit_transform(data_test))
mape = mean_absolute_percentage_error(y_test, preds)
print(f"mape = {round(mape, 3)}")
mape = 0.227